#! user/bin/python
# -*- coding: utf-8 -*-

"""
Created on 2021/8/27 8:51

@File: 邮件获取_发送_解析和编写的综合运用_pymail.py
@Author: LT
@Description: 用户可以通过命令行控制台输入命令来读取、发送和删除电子邮件信息
              利用poplib模块查看POP电子邮件信息，使用smtplib发送新邮件；
              使用email模块包来解析提前poplib抓取的邮件题头及负载和撰写要用smtplib发送的邮件

              运行后会先进入python shell模式，然后再在这个环境下敲击命令进行交互
"""
import poplib                                       # 抓取邮件用
import smtplib                                      # 发送邮件用
from .. import 邮件配置模块_mailconfig                # 导入配置
from email import utils
from email.parser import Parser                     # 解析poplib抓取的邮件用
from email.message import Message                   # 生成smtplib发送的邮件主体用

fetchEncoding = 邮件配置模块_mailconfig.fetchEncoding


def decodeToUnicode(messageBytes, fetchEncoding=fetchEncoding):
    """
    因为poplib抓取过来的是bytes类型，所以这里将抓取来的bytes类型解码成str类型的Unicode字符串，是为了方便解析；
    Python3.2往后的版本不需要这样：这种情况下信息不做变换，直接返回即可
    """
    return [line.decode(fetchEncoding) for line in messageBytes]


def splitaddrs(field: '很英文习惯的 (姓名, 地址)'):
    """
    在都好后分割地址列表，允许姓名部分中存在逗号
    """
    pairs = utils.getaddresses([field])
    return [utils.formataddr(pair) for pair in pairs]       # 变成[(姓名 <地址>)]


def inputmessage():
    import sys
    From = input('From?').strip()
    To = input('To?').strip()                               # 题头的时间日期可以自动设置
    To = splitaddrs(To)                                     # 可以有多个地址，可以用姓名 <地址>地址对；允许使用逗号作为分隔符及姓名中的字符
    Subj = input('Subj?').strip()                           # 输入主题
    print('Please type here the message text, end with line="."')
    text = ""
    while True:
        line = sys.stdin.readline()
        print("You just input =>", repr(line), '--->Just input only .\n to quit edit text.')
        if line == '.\n':
            break
        text += line
    return From, To, Subj, text


def sendmessage():
    """
    发送邮件，邮件分为：题头；负载；附件（可选）
    """
    From, To, Subj, text = inputmessage()
    # 实例化邮件主体
    msg = Message()
    # 邮件题头部分
    msg['From'] = From
    msg['To'] = ', '.join(To)
    msg['Subject'] = Subj                                   # 对题头而非发送内容进行合并
    msg['Date'] = utils.formatdate()                        # 当前时间日期，rfc2822
    # 邮件负载部分
    msg.set_payload(text)

    # 发送邮件也需要登录smtp服务器
    server = smtplib.SMTP(邮件配置模块_mailconfig.smtpservername)
    server.login(邮件配置模块_mailconfig.smtpusername, 邮件配置模块_mailconfig.qq_pop_smtp_authorized_code)
    try:
        failed = server.sendmail(From, To, str(msg))        # 捕捉可能的异常
    except Exception as e:
        print('Error - sendmail occurs because', e)
    else:
        if failed:
            print('Failed', failed)


def connect(servername, user, passwd):
    print('Connecting...')
    server = poplib.POP3(servername)
    server.user(user)                                       # 连接，登入邮件服务器
    server.pass_(passwd)                                    # pass是python保留字，所以它用了pass_
    print(server.getwelcome())                              # 打印欢迎信息
    return server


def loadmessages(servername, user, passwd, loadfrom=1):
    """
    顾名思义，加载诸多信息；这是总览即将要抓取的邮件的总信息
    """
    server = connect(servername, user, passwd)
    try:
        print(server.list())
        (msgCount, msgBytes) = server.stat()                # 获取目标邮件总信息，(总共有多少邮件，邮件总共有多少字节数)
        print('There are', msgCount, 'mail messages in', msgBytes, 'bytes')
        print('Retrieving...')
        msgList = []
        for i in range(loadfrom, msgCount + 1):
            (hdr, message, octets) = server.retr(i)         # 用邮件编号按序抓取邮件；编号从1开始
            message = decodeToUnicode(message)
            msgList.append('\n'.join(message))
    finally:
        server.quit()                                       # 无论如何也要解锁邮箱
    assert len(msgList) == (msgCount - loadfrom) + 1        # 当抓取完后发现已抓取的信息数与目标抓取的总信息数不等则引发异常
    return msgList


def deletemessage(servername, user, passwd, toDelete, verify=True):
    """顾名思义，这是个删除指定信息的函数"""
    print('To be deleted:', toDelete)
    if verify and input('Are you sure to Delete?')[:1] not in ['y', 'Y']:
        print('Delete cancelled')
    else:
        server = connect(servername, user, passwd)
        try:
            print('Deleting message from server...')
            for msgnum in toDelete:
                server.dele(msgnum)                         # 和接收类似，删除也是要通过邮件编号的
        finally:
            server.quit()                                   # 邮箱在quit()之前一直处于锁定状态


def showindex(msgList):
    count = 0                                               # 显示部分邮件题头
    for msgtext in msgList:
        msghdrs = Parser().parsestr(msgtext, headersonly=True)
        count += 1
        print('%d:\t%d bytes' % (count, len(msgtext)))
        for hdr in ('From', 'To', 'Date', 'Subject'):
            try:
                print('\t%-8s=>%s' % (hdr, msghdrs[hdr]))
            except KeyError:
                print('\t%-8s=>(unknown)' % hdr)
        if count % 5 == 0:
            input('[Press Enter key]')                      # 每输入5个后暂停一次


def showmessage(i, msgList):
    """
    :param i: 要展现的邮件索引
    :param msgList: 邮件信息列表
    :return: None
    """
    if 1 <= i <= len(msgList):
        print(f'No.{i} ' + '↓ ' * 75)
        msg = Parser().parsestr(msgList[i - 1])
        content = msg.get_payload()                         # 打印负载：字符串，或[消息]
        if isinstance(content, str):                        # 只保留末尾的行尾符
            content = content.rstrip() + '\n'
        print(content)
        print('↑ ' * 80)
    else:
        print('Bad message number')


def savemessage(i, mailfile, msgList):
    """
    :param i: 要保存的邮件索引
    :param mailfile: 保存成的文件路径名
    :param msgList: 消息列表
    :return: None
    """
    if 1 <= i <= len(msgList):
        savefile = open(mailfile, 'a', encoding=fetchEncoding)      # a是读写都可以，+是追加
        savefile.write('\n' + msgList[i - 1] + '-' * 80 + '\n')
    else:
        print('Bad message number')


def msgnum(command):
    """从命令行中获取用户输入的指定的消息编号"""
    try:
        return int(command.split()[1])
    except:
        return -1                                                   # 设定-1为失败


helptext = """
Available commands:
i     - index display
l n?  - list all messages (or just message n)
d n?  - mark all messages for deletion (or just message n)
s n?  - save all messages to a file (or just message n)
m     - compose and send a new mail message
q     - quit pymail
?     - display this help text
"""


def interact(msgList, mailfile):
    # showindex(msgList)
    toDelete = []
    while True:
        try:
            command = input('[Pymail] Action? (i, l, d, s, m ,q, ?)')
        except EOFError:
            command = 'q'                                           # 命令中敲入ctrl + d也等效于输入'q'
        if not command:
            command = '*'

        # 退出
        if command == 'q':
            break

        # 索引
        elif command[0] == 'i':
            showindex(msgList)

        # 列表
        elif command[0] == 'l':
            if len(command) == 1:
                for i in range(1, len(msgList) + 1):
                    showmessage(i, msgList)
            else:
                showmessage(msgnum(command), msgList)

        # 保存
        elif command[0] == 's':
            if len(command) == 1:
                for i in range(1, len(msgList) + 1):
                    savemessage(i, mailfile, msgList)
            else:
                savemessage(msgnum(command), mailfile, msgList)

        # 删除——注意，这里一旦将要删除的邮件索引加进去后，就不能去掉；未完善撤销功能
        elif command[0] == 'd':
            if len(command) == 1:
                toDelete = list(range(1, len(msgList) + 1))         # 指定删除的信息编号列表
            else:
                delnum = msgnum(command)
                # 简单地检验用户输入的删除编号是否合理
                if (1 <= delnum <=len(msgList)) and (delnum not in toDelete):
                    toDelete.append(delnum)
                else:
                    print('Bad message number, please check.')

        # 邮件
        elif command[0] == 'm':
            sendmessage()

        # 帮助
        elif command[0] == '?':
            print(helptext)

        # 其他输入
        else:
            print('Need help? Type "?" to show commands help.')
    return toDelete


if __name__ == '__main__':
    import getpass              # 用了这个就得冲cmd命令行启动了
    popmailserver = 邮件配置模块_mailconfig.popservername
    popmailuser = 邮件配置模块_mailconfig.popusername
    mailfile = 邮件配置模块_mailconfig.savemailfile
    mailpswd = 邮件配置模块_mailconfig.qq_pop_smtp_authorized_code
    print('[Pymail email client]')
    msgList = loadmessages(popmailserver, popmailuser, mailpswd)
    toDelete = interact(msgList, mailfile)
    if toDelete:
        deletemessage(popmailserver, popmailuser, mailpswd, toDelete)
    print('Bye~')
